package com.springboot.limitSpring.utils;

import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisNoScriptException;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;

//单例redis集群工具类
public class JedisUtil {
    private Logger logger = LoggerFactory.getLogger(JedisUtil.class);
    protected int DEFAULT_EXPIRED_TIME_SECONDS_OBJECT = Integer.valueOf(PropertyUtil.getProperty("redis.expireSeconds"));
    private volatile String unlockSha1 = "";
    private static final Long UNLOCK_SUCCESS_CODE = 1L;
    private static final String LOCK_SUCCESS_CODE = "ok";
    /**
     * lua脚本：判断锁住值是否为当前线程持有，是的话解锁，不是的话解锁失败
     */
    private static final String DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL = "if" +
            " redis.call('get', KEYS[1]) == ARGV[1]" +
            " then" +
            " return redis.call('del', KEYS[1])" +
            " else" +
            " return 0" +
            " end";

    private JedisCluster jedisCluster = null;
    private static JedisUtil jedisUtil = null;

    private JedisUtil() {
        String[] serverArray = PropertyUtil.getProperty("redis.clusterNodes").split(",");//获取服务器数组(这里要相信自己的输入，所以没有考虑空指针问题)
        Set<HostAndPort> nodes = new HashSet<>();
        for (String ipPort : serverArray) {
            String[] ipPortPair = ipPort.split(":");
            nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
        }

        jedisCluster = new JedisCluster(nodes, Integer.valueOf(PropertyUtil.getProperty("redis.commandTimeout")), 1000, 1, new GenericObjectPoolConfig());//需要密码连接的创建对象方式
    }

    public static JedisUtil getInstance() {
        if (jedisUtil == null) {
            synchronized (JedisUtil.class) {
                if (jedisUtil == null) {
                    jedisUtil = new JedisUtil();
                }
            }
        }
        return jedisUtil;

    }

    /**
     * 从jedis连接池中获取获取jedis对象
     *
     * @return
     */
    public Map<String, JedisPool> getJedis() {
        return jedisCluster.getClusterNodes();
    }

    /**
     * 获取JedisUtil实例
     *
     * @return
     */
    public JedisCluster getJedisCluster() {
        return jedisCluster;
    }


    public boolean putString(String sKey, String sValue, int expiredInSeconds) {
        boolean result = false;

        if (sKey == null || sKey == "") {
            return result;
        }

        if (this.jedisCluster != null) {
            try {
                this.jedisCluster.set(sKey, sValue);
                if (expiredInSeconds > 0) {
                    this.jedisCluster.expire(sKey, expiredInSeconds);
                }

                result = true;
            } catch (Exception e) {
                return false;
            }
        }
        return result;
    }

    public boolean putString(String sKey, String sValue) {
        return putString(sKey, sValue, this.DEFAULT_EXPIRED_TIME_SECONDS_OBJECT);
    }

    public String getString(String sKey) {
        String result = null;
        if (sKey == null || sKey == "") {
            return result;
        }

        if (this.jedisCluster != null) {
            try {
                result = this.jedisCluster.get(sKey);
            } catch (Exception e) {
                return e.getMessage();
            }
        }

        return result;
    }

    public void decrBy(String sKey, int num) {
        if (sKey == null || sKey == "") {
            return;
        }
        if (this.jedisCluster != null) {
            try {
                this.jedisCluster.decrBy(sKey, num);
            } catch (Exception e) {
                return;
            }

        }
        return;
    }

    public long ttl(String sKey) {
        long result = -1;
        if (sKey == null || sKey == "") {
            return result;
        }
        if (this.jedisCluster != null) {
            try {
                result = this.jedisCluster.ttl(sKey);
            } catch (Exception e) {
                return result;
            }

        }

        return result;
    }

    public boolean putObject(String sKey, Serializable object, int expiredInSeconds) {
        boolean result = false;

        if (sKey == null || sKey == "") {
            return result;
        }

        if (this.jedisCluster != null) {
            byte[] datas = SerializationUtils.serialize(object);
            byte[] arrKey = sKey.getBytes();
            try {
                this.jedisCluster.set(arrKey, datas);
                if (expiredInSeconds > 0) {
                    this.jedisCluster.expire(arrKey, expiredInSeconds);
                    result = true;
                }
            } catch (Exception e) {
                return false;
            }
        }

        return result;
    }

    public boolean putObject(String sKey, Serializable object) {
        return putObject(sKey, object, this.DEFAULT_EXPIRED_TIME_SECONDS_OBJECT);
    }

    public <T> T getObject(String sKey, Class<T> oclass) {
        Object result = null;
        if (sKey == null || sKey == "") {
            return (T) result;
        }

        if (this.jedisCluster != null) {
            byte[] arrKey = sKey.getBytes();

            byte[] datas = this.jedisCluster.get(arrKey);

            if ((datas != null) && (datas.length > 0)) {
                result = SerializationUtils.deserialize(datas);
            }
        }

        return (T) result;
    }

    public boolean putMap(String sKey, Map<String, String> oMap, int expiredInSeconds) {
        boolean result = false;

        if ((sKey == null || sKey == "") || (oMap == null) || (oMap.size() <= 0)) {
            return result;
        }

        if (this.jedisCluster != null) {
            try {
                this.jedisCluster.hmset(sKey, oMap);
                if (expiredInSeconds > 0) {
                    this.jedisCluster.expire(sKey, expiredInSeconds);
                }
                result = true;
            } catch (Exception e) {
                return false;
            }
        }
        return result;
    }

    public boolean putMap(String sKey, Map<String, String> oMap) {
        return putMap(sKey, oMap, this.DEFAULT_EXPIRED_TIME_SECONDS_OBJECT);
    }

    public boolean putMap(String sKey, String sField, String sValue, int expiredInSeconds) {
        boolean result = false;

        if ((sKey == null || sKey == "") || (sField == null || sField == "")) {
            return result;
        }

        if (this.jedisCluster != null) {
            try {
                this.jedisCluster.hset(sKey, sField, sValue);
                if (expiredInSeconds > 0) {
                    this.jedisCluster.expire(sKey, expiredInSeconds);
                }
                result = true;
            } catch (Exception e) {
                return false;
            }
        }
        return result;
    }

    public boolean putMap(String sKey, String sField, String sValue) {
        return putMap(sKey, sField, sValue, this.DEFAULT_EXPIRED_TIME_SECONDS_OBJECT);
    }

    public Map<String, String> getMap(String sKey) {
        Map result = null;
        if (sKey == null || sKey == "") {
            return result;
        }

        if (this.jedisCluster != null) {
            try {
                result = this.jedisCluster.hgetAll(sKey);
            } catch (Exception e) {
                return null;
            }

        }

        return result;
    }

    public Map<String, String> getMap(String sKey, String[] fields) {
        Map result = null;

        Map mapAll = getMap(sKey);
        if ((mapAll != null) && (mapAll.size() > 0) && (fields != null) && (fields.length > 0)) {
            result = new LinkedHashMap();
            for (String field : fields) {
                result.put(field, mapAll.get(field));
            }

        }

        return result;
    }

    public boolean existsMapItem(String sKey, String sField) {
        if ((sKey == null || sKey == "") || (sField == null || sField == "")) {
            return false;
        }

        if (this.jedisCluster != null) {
            try {
                return this.jedisCluster.hexists(sKey, sField).booleanValue();
            } catch (Exception e) {
                return false;
            }
        }

        return false;
    }

    public boolean putMapValueAsObject(String sKey, String sField, Serializable oValue, int expiredInSeconds) {
        boolean result = false;

        if ((sKey == null || sKey == "") || (sField == null || sField == "")) {
            return result;
        }

        if (this.jedisCluster != null) {
            byte[] arrKey = sKey.getBytes();
            try {
                this.jedisCluster.hset(arrKey, sField.getBytes(), SerializationUtils.serialize(oValue));
                if (expiredInSeconds > 0) {
                    this.jedisCluster.expire(arrKey, expiredInSeconds);
                }

                result = true;
            } catch (Exception e) {
                return false;
            }
        }
        return result;
    }

    public boolean putMapValueAsObject(String sKey, String sField, Serializable oValue) {
        return putMapValueAsObject(sKey, sField, oValue, this.DEFAULT_EXPIRED_TIME_SECONDS_OBJECT);
    }

    public <T> T getMapValueAsObject(String sKey, String sField, Class<T> oClass) {
        Object result = null;

        if ((sKey == null || sKey == "") || (sField == null || sField == "")) {
            return (T) result;
        }

        if (this.jedisCluster != null) {
            try {
                byte[] datas = this.jedisCluster.hget(sKey.getBytes(), sField.getBytes());
                if ((datas != null) && (datas.length > 0)) {
                    Object tmpObject = SerializationUtils.deserialize(datas);
                    result = tmpObject;
                }
            } catch (Exception e) {
                return null;
            }
        }
        return (T) result;
    }

    public void remove(String sKey) {
        if (sKey == null || sKey == "") {
            return;
        }

        if (this.jedisCluster != null) {
            try {
                this.jedisCluster.del(sKey);
            } catch (Exception e) {
                return;
            }
        }

    }

    public void close() {
        if (this.jedisCluster == null)
            return;
        try {
            this.jedisCluster.close();
        } catch (IOException localIOException) {
        }
    }

    public boolean tryLock(String lockKey, String lockVal, long expiryTime, long loopTryTime) {
        Long endTime = System.currentTimeMillis() + loopTryTime;
        while (System.currentTimeMillis() < endTime) {
            if (tryLock(lockKey, lockVal, expiryTime)) {
                return true;
            }

        }
        return false;
    }

    public boolean tryLock(String lockKey, String lockVal, long expiryTime, int retryTimes,
                           long setpTime) {
        while (retryTimes > 0) {
            if (tryLock(lockKey, lockVal, expiryTime)) {
                return true;
            }
            retryTimes--;
            try {
                Thread.sleep(setpTime);
            } catch (InterruptedException e) {
                logger.error("get distribute lock error" + e.getLocalizedMessage());

            }
        }
        return false;
    }


    public boolean tryLock(String lockKey, String lockVal, long expiryTime) {
        //相比一般的分布式锁，这里把setNx和setExpiry操作合并到一起，jedis保证原子性，避免连个命令之间出现宕机等问题
        //这里也可以我们使用lua脚本实现
        String result;
        try{
             result = jedisCluster.set(lockKey, lockVal, "NX", "PX", expiryTime);
        }catch (Exception e){
            return false;
        }

        return LOCK_SUCCESS_CODE.equalsIgnoreCase(result);
    }

    public boolean tryUnLock(String lockKey, String lockVal) {
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        List<String> argv = new ArrayList<>();
        argv.add(lockVal);
        try {
            Object result = jedisCluster.evalsha(unlockSha1, keys, argv);
            return UNLOCK_SUCCESS_CODE.equals(result);
        } catch (JedisNoScriptException e) {
            //没有脚本缓存时，重新发送缓存
            logger.info("try to store script......");
            storeScript(lockKey);
            Object result = jedisCluster.evalsha(unlockSha1, keys, argv);
            return UNLOCK_SUCCESS_CODE.equals(result);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public void storeScript(String slotKey) {
        if (StringUtils.isEmpty(unlockSha1) || !jedisCluster.scriptExists(unlockSha1, slotKey)) {
            //redis支持脚本缓存，返回哈希码，后续可以继续用来调用脚本
            unlockSha1 = jedisCluster.scriptLoad(DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL, slotKey);
        }
    }


}

